/*******************************************************************************
 *
 *    Copyright (c) Baina Info Tech Co. Ltd
 *
 *    HttpClient
 *
 *    @author: dhu, chzhong
 *    @since:  2010-3-17
 *    @version: 1.0
 *
 ******************************************************************************/
package com.sdk.network;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Date;

import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.text.TextUtils;

import com.sdk.util.Log;


public class HttpClient extends DefaultHttpClient {

    private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB6";


    /** Set if HTTP requests are blocked from being executed on this thread */
    private static final ThreadLocal<Boolean> sThreadBlocked =
        new ThreadLocal<Boolean>();

    /** Interceptor throws an exception if the executing thread is blocked */
    private static final HttpRequestInterceptor sThreadCheckInterceptor =
        new HttpRequestInterceptor() {
        public void process(final HttpRequest request, final HttpContext context) {
            if (sThreadBlocked.get() != null && sThreadBlocked.get()) {
                throw new RuntimeException("This thread forbids HTTP requests");
            }
        }
    };


    private static final String TAG = HttpClient.class.getSimpleName();

    private boolean mCookieEnable = false;

    public boolean isCookieEnable() {
        return mCookieEnable;
    }

    /**
     * Change the SSL host name verifier to allowing all hosts.
     */
    public void enableAllowAllVerifier() {
        final ClientConnectionManager connectionManager = getConnectionManager();
        final SchemeRegistry registry = connectionManager.getSchemeRegistry();
        final Scheme httpsScheme = registry.getScheme("https");
        if (httpsScheme != null) {
            final SocketFactory socketFactory = httpsScheme.getSocketFactory();
            if (socketFactory instanceof SSLSocketFactory) {
                ((SSLSocketFactory)socketFactory).setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            }
        }
    }

    /**
     * Replace the HTTPS schema socket factory with the default {@linkplain SSLSocketFactory}, thus the {@linkplain HttpClient} will use the default security settings.
     */
    public void enableSSLSecurity() {
        final ClientConnectionManager connectionManager = getConnectionManager();
        final SchemeRegistry registry = connectionManager.getSchemeRegistry();
        if (registry != null) {
            final Scheme httpsScheme = registry.getScheme("https");
            if (httpsScheme != null) {
                final SocketFactory socketFactory = httpsScheme.getSocketFactory();
                if (socketFactory instanceof SSLSocketFactory) {
                    return;
                }
                registry.unregister("https");
            }
            registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        }
    }


    /**
     * Replace the HTTPS schema socket factory with {@linkplain DummySSLSocketFactory}, thus the {@linkplain HttpClient} will trust all SSL links.
     */
    public void disableSSLSecurity() {
        final ClientConnectionManager connectionManager = getConnectionManager();
        if (connectionManager != null) {
            final SchemeRegistry registry = connectionManager.getSchemeRegistry();
            if (registry != null) {
                final Scheme httpsScheme = registry.getScheme("https");
                if (httpsScheme != null) {
                    final SocketFactory socketFactory = httpsScheme.getSocketFactory();
                    if (socketFactory instanceof DummySSLSocketFactory) {
                        return;
                    }
                    registry.unregister("https");
                }
                registry.register(new Scheme("https", new DummySSLSocketFactory(), 443));
            }
        }

    }


    /**
     * Change the SSL host name verifier to browser compatible settings. This is the default.
     */
    public void enableBrowserCompatibleVerifier() {
        final ClientConnectionManager connectionManager = getConnectionManager();
        final SchemeRegistry registry = connectionManager.getSchemeRegistry();
        final Scheme httpsScheme = registry.getScheme("https");
        if (httpsScheme != null) {
            final SocketFactory socketFactory = httpsScheme.getSocketFactory();
            if (socketFactory instanceof SSLSocketFactory) {
                ((SSLSocketFactory)socketFactory).setHostnameVerifier(SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            }
        }
    }

    /**
     * Change the SSL host name verifier to strict settings.
     */
    public void enableStrictVerifier() {
        final ClientConnectionManager connectionManager = getConnectionManager();
        final SchemeRegistry registry = connectionManager.getSchemeRegistry();
        final Scheme httpsScheme = registry.getScheme("https");
        if (httpsScheme != null) {
            final SocketFactory socketFactory = httpsScheme.getSocketFactory();
            if (socketFactory instanceof SSLSocketFactory) {
                ((SSLSocketFactory)socketFactory).setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
            }
        }
    }

    public void setCookieEnable(final boolean cookieEnable) {
        mCookieEnable = cookieEnable;
    }


    /**
     * Retrieve the content of the URL as string.
     * @param url The URL to retrieve.
     * @return The content of the given URL, or null if error occurred.
     */
    public static String getUrlAsString(final String url) {
        try {
            final HttpGet get = new HttpGet(url);
            final HttpClient httpClient = HttpClient.newInstance();
            final HttpResponse response = httpClient.execute(get);
            final int statusCode = response.getStatusLine().getStatusCode();
            if(HttpStatus.SC_OK == statusCode){
                //get response String
                return EntityUtils.toString(response.getEntity());
            } else {
                Log.e(TAG, "Server reply error : %s", response.getStatusLine());
            }
        } catch (final Exception e) {
            Log.e(TAG, "request error " + e.toString());
            e.printStackTrace();
        }
        return null;
    }


    /**
     * Retrieve the content of the URL as {@linkplain JSONObject}.
     * @param url The URL to retrieve.
     * @return The content of the given URL, or null if error occurred.
     */
    public static JSONObject getUrlAsJSONObject(final String url) {
        final String json = getUrlAsString(url);
        if (json != null) {
            try {
                return new JSONObject(json);
            } catch (final JSONException e) {
                Log.e(TAG, "Failed to parse string as JSON.");
                Log.e(TAG, "--> %s", json);
                e.printStackTrace();
            }
        }
        return null;
    }


    /**
     * Retrieve the content of the URL as {@linkplain JSONArray}.
     * @param url The URL to retrieve.
     * @return The content of the given URL, or null if error occurred.
     */
    public static JSONArray getUrlAsJSONArray(final String url) {
        final String json = getUrlAsString(url);
        if (json != null) {
            try {
                return new JSONArray(json);
            } catch (final JSONException e) {
                Log.e(TAG, "Failed to parse string as JSON array.");
                Log.e(TAG, "--> %s", json);
                e.printStackTrace();
            }
        }
        return null;
    }


    public static String encodedUrl(final String url){
        String encodedUrl = url;
        if(!TextUtils.isEmpty(url)){
            final StringBuilder urlBuilder = new StringBuilder();
            final int index = url.lastIndexOf("?");
            String path = "";
            String params = "";
            if(index != -1){
                path = url.substring(0, index);
                params = url.substring(index+1);
            }else{
                path = url;
            }
            final String[] pathSegment = path.split("/");
            if(null != pathSegment && pathSegment.length > 0){
                for(int i = 0;i<pathSegment.length;i++){
                    final String currentPathSegment = pathSegment[i];
                    if("http:".equalsIgnoreCase(currentPathSegment) || "https:".equals(currentPathSegment)){
                        urlBuilder.append(currentPathSegment);
                    }else{
                        urlBuilder.append(URLEncoder.encode(currentPathSegment));
                    }
                    if(i < pathSegment.length -1){
                        urlBuilder.append("/");
                    }
                }
            }
            if(!TextUtils.isEmpty(params)){
                urlBuilder.append("?");
                final String[] paramesSegment = params.split("&");
                if(null != paramesSegment && paramesSegment.length > 0){
                    for(int i = 0;i<paramesSegment.length;i++){
                        final String currentParame = paramesSegment[i];
                        final int equalIndex = currentParame.indexOf("=");
                        if(equalIndex != -1){
                            urlBuilder.append(currentParame.substring(0, equalIndex));
                            urlBuilder.append("=");
                            urlBuilder.append(currentParame.substring(equalIndex + 1));
                        }
                        if(i < paramesSegment.length -1){
                            urlBuilder.append("&");
                        }
                    }
                }
            }
            encodedUrl = urlBuilder.toString();
            Log.d(TAG, "url after encode:"+encodedUrl);
        }
        return encodedUrl;
    }


    /**
     * Download specified url directly and save it to local path.
     * @param url the url of the file.
     * @param saveToFile the file to save to.
     * @return true if success, false otherise.
     */
    public static boolean downloadFile(final String url, final File saveToFile) {
        boolean success = false;
        if (null == saveToFile) {
            throw new IllegalArgumentException("saveToFile may not be null.");
        }
        if (!TextUtils.isEmpty(url)) {
            if (saveToFile.exists()) {
                saveToFile.delete();
            }
            FileOutputStream fos = null;
            try {
                final HttpGet get = new HttpGet(url);
                final HttpClient httpClient = HttpClient.newInstance();
                final HttpResponse response = httpClient.execute(get);
                final int statusCode = response.getStatusLine().getStatusCode();
                if(HttpStatus.SC_OK == statusCode){
                    //get response String
                    fos = new FileOutputStream(saveToFile);
                    response.getEntity().writeTo(fos);
                    if(saveToFile.length() != 0){
                        success = true;
                    }else if(saveToFile.length() == 0 && saveToFile.exists()){
                        saveToFile.delete();
                        success = false;
                    }
                } else {
                    Log.e(TAG, "server reply error:" + statusCode);
                }
            } catch (final Exception e) {
                Log.e(TAG, "request error " + e.toString());
                e.printStackTrace();
            } finally {
                quietClose(fos);
            }
        }
        return success;

    }
    /**
     * Create a new HttpClient with reasonable defaults (which you can update).
     * @param userAgent to report in your HTTP requests.
     * @param ignoreSSLSecurity if true, SSL will trust all certificates, otherwise SSL will check certificates.
     * @return HttpClient for you to use for all your requests.
     */
    public static HttpClient newInstance(String userAgent, final boolean ignoreSSLSecurity) {
        final HttpParams params = new BasicHttpParams();

        // Turn off stale checking.  Our connections break all the time anyway,
        // and it's not worth it to pay the penalty of checking every time.
        HttpConnectionParams.setStaleCheckingEnabled(params, false);

        // Default connection and socket timeout of 20 seconds.  Tweak to taste.
        HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
        HttpConnectionParams.setSoTimeout(params, 20 * 1000);
        HttpConnectionParams.setSocketBufferSize(params, 8192);

        // Don't handle redirects -- return them to the caller.  Our code
        // often wants to re-POST after a redirect, which we must do ourselves.
        HttpClientParams.setRedirecting(params, true);

        // Set the specified user agent and register standard protocols.
        if(TextUtils.isEmpty(userAgent)){
            userAgent = DEFAULT_USER_AGENT;
        }
        HttpProtocolParams.setUserAgent(params, userAgent);
        final SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http",
                                           PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", ignoreSSLSecurity ? new DummySSLSocketFactory() : SSLSocketFactory.getSocketFactory(), 443));

        final ClientConnectionManager manager =
            new ThreadSafeClientConnManager(params, schemeRegistry);


        // We use a factory method to modify superclass initialization
        // parameters without the funny call-a-static-method dance.
        return new HttpClient(manager, params);
    }

    /**
     * Create a new HttpClient with reasonable defaults (which you can update).
     * @return HttpClient for you to use for all your requests.
     */
    public static HttpClient newInstance() {
        return newInstance(null, false);
    }

    /**
     * Create a new HttpClient with reasonable defaults (which you can update).
     * @param ignoreSSLSecurity if true, SSL will trust all certificates, otherwise SSL will check certificates.
     * @return HttpClient for you to use for all your requests.
     */
    public static HttpClient newInstance(final boolean ignoreSSLSecurity) {
        return newInstance(null, ignoreSSLSecurity);
    }


    /**
     * Create a new HttpClient with reasonable defaults (which you can update).
     * @param userAgent to report in your HTTP requests.
     * @return HttpClient for you to use for all your requests.
     */
    public static HttpClient newInstance(final String userAgent) {
        return newInstance(userAgent, false);
    }

    private HttpClient(final HttpParams params) {
        super(params);
    }

    private HttpClient(final ClientConnectionManager conman, final HttpParams params) {
        super(conman, params);
    }

    @Override
    protected BasicHttpProcessor createHttpProcessor() {
        // Add interceptor to prevent making requests from main thread.
        final BasicHttpProcessor processor = super.createHttpProcessor();
        processor.addRequestInterceptor(sThreadCheckInterceptor);

        return processor;
    }

    @Override
    protected HttpContext createHttpContext() {
        // Same as DefaultHttpClient.createHttpContext() minus the
        // cookie store.
        final HttpContext context = new BasicHttpContext();
        context.setAttribute(
                             ClientContext.AUTHSCHEME_REGISTRY,
                             getAuthSchemes());
        context.setAttribute(
                             ClientContext.COOKIESPEC_REGISTRY,
                             getCookieSpecs());
        context.setAttribute(
                             ClientContext.CREDS_PROVIDER,
                             getCredentialsProvider());
        if(mCookieEnable){
            context.setAttribute(ClientContext.COOKIE_STORE, getCookieStore());
        }
        return context;
    }

    /**
     * Close an {@linkplain Closeable} quietly.
     * @param closeable the {@linkplain Closeable} to close.
     */
    public static final void quietClose(final Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (final IOException e) {
                // Ignore errors.
            }
        }
    }

    /**
     * Block this thread from executing HTTP requests.
     * Used to guard against HTTP requests blocking the main application thread.
     * @param blocked if HTTP requests run on this thread should be denied
     */
    public static void setThreadBlocked(final boolean blocked) {
        sThreadBlocked.set(blocked);
    }

    /**
     * Get a String for "Set-Cookie" head from a cookie
     * @param cookie
     * @return
     */
    public static String getSetCookieString(final Cookie cookie) {
        String setCookie = null;
        if (null != cookie) {
            final StringBuilder builder = new StringBuilder();
            builder.append(cookie.getName());
            builder.append("=");
            builder.append(cookie.getValue());

            final Date expires = cookie.getExpiryDate();
            if(null != expires){
                builder.append(";expires=");
                builder.append(expires.toGMTString());
            }
            builder.append(";path=");
            builder.append(cookie.getPath());
            builder.append(";domain=");
            builder.append(cookie.getDomain());
            if(cookie.isSecure()){
                builder.append(";Secure");
            }
            setCookie = builder.toString();
        }
        return setCookie;
    }

    /**
     * Post content to the given url.
     * @param url The url to post to.
     * @param content the content to post.
     */
    public static void postContent(final String url, final String content) {
        try {
            final HttpPost post = new HttpPost(url);
            final HttpClient httpClient = HttpClient.newInstance();
            final StringEntity entity = new StringEntity(content, HTTP.UTF_8);
            entity.setContentEncoding(HTTP.UTF_8);
            post.setEntity(entity);
            final HttpResponse response = httpClient.execute(post);
            final int statusCode = response.getStatusLine().getStatusCode();
            if(HttpStatus.SC_OK == statusCode){
                //get response String
            }
            else {
            }
        } catch (final Exception e) {
        }
    }

}
